package de.ret_world.openrpgstudio.mapeditor;

import buoy.event.*;
import buoy.widget.*;
import de.ret_world.openrpgstudio.editor.Database;
import de.ret_world.openrpgstudio.editor.Main;
import de.ret_world.openrpgstudio.editor.Media;
import de.ret_world.openrpgstudio.editor.Project;
import de.ret_world.openrpgstudio.editor.gui.ScriptArea;
import de.ret_world.openrpgstudio.editor.helpers.Internationalization;
import de.ret_world.openrpgstudio.editor.helpers.ScriptTools;
import de.ret_world.openrpgstudio.editor.plugininterfaces.MainWindowPlugin;
import de.ret_world.openrpgstudio.editor.script.ScriptObject;
import de.ret_world.openrpgstudio.mapeditor.scripts.*;
import de.ret_world.openrpgstudio.mapeditor.tools.BrushTool;
import de.ret_world.openrpgstudio.mapeditor.tools.RubberTool;
import java.awt.Insets;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaThread;
import org.luaj.vm2.lib.LibFunction;
import org.luaj.vm2.lib.jse.JsePlatform;

/**
 * The main class of the MapEditor plugin. This is a fully functional TileMap Editor. It is extendable with Sub-Plugins for Tools that help creating
 * TileMaps!
 *
 * @author Emu
 */
public class MapEditor extends MainWindowPlugin {

    /**
     * This plug-in ID.
     */
    public static final String PLUGIN_ID = "de.ret_world.openrpgstudio.mapeditor";
    //Variables
    private MapEditorWidget editor;
    private BList lstMaps;
    private BList lstLayers;
    private TileSetWidget wTileSet;
    private BTabbedPane tabbed;
    private ScriptArea script;
    private BFrame parent;
    
    @Override
    protected void doStart() throws Exception {
        super.doStart();
        if (Project.isProjectLoaded()) {
            Project project = Project.getInstance();
            
//            project.getScriptRegister().registerLibrary("TileSetLib", new TileSetLib());
//            project.getScriptRegister().registerLibrary("TileMapLib", new TileMapLib());
            
            if (!project.getScriptRegister().isScriptRegistered("tileset")) {
                String scrpt = "-- OpenRPGStudio TileSet Script --\n"
                        + "-- Script is generated by application! Change with care! --\n\n";
                ScriptTools.getInstance().saveScriptFile(project.getFolder("scripts").resolve("tilesets.lua"), scrpt);
                project.getScriptRegister().registerScript("tileset", Paths.get("tilesets.lua"), -10);
            }
            
            //ScriptTools.getInstance().executeStartScript();
        }
    }
    
    @Override
    public void init(BorderContainer container, BFrame parent) {
        this.parent = parent;
        Internationalization intn = Internationalization.getInstance();
        
        HashMap<String, ScriptObject> scriptMap = Project.getInstance().getDatabase().getScriptMap(TileMap.TYPE);
        Collection<ScriptObject> tilemaps;
        if (scriptMap == null) {
            tilemaps = new ArrayList<>();
        } else {
            tilemaps = scriptMap.values();
        }
        
        LayoutInfo li = new LayoutInfo(LayoutInfo.CENTER, LayoutInfo.HORIZONTAL, new Insets(5, 5, 5, 5), null);
        
        FormContainer down = new FormContainer(new double[]{1, 1}, new double[]{0, 1, 0});
        
        down.add(new BLabel(intn.getString("ME.MAPS")), 0, 0, li);
        BScrollPane scrl = new BScrollPane();
        lstMaps = new BList(tilemaps);
        lstMaps.addEventLink(SelectionChangedEvent.class, this, "mapChanged");
        scrl.setContent(lstMaps);
        scrl.setForceHeight(true);
        scrl.setForceWidth(true);
        down.add(BOutline.createEtchedBorder(scrl, false), 0, 1, new LayoutInfo(LayoutInfo.CENTER, LayoutInfo.BOTH, new Insets(5, 5, 5, 5), null));
        
        RowContainer rc = new RowContainer();
        BButton btn = new BButton(new ImageIcon(manager.getPluginClassLoader(manager.getRegistry().getPluginDescriptor(Main.PLUGIN_ID)).getResource("plus.png")));
        btn.addEventLink(CommandEvent.class, this, "addMap");
        rc.add(btn, li);
        btn = new BButton(new ImageIcon(manager.getPluginClassLoader(manager.getRegistry().getPluginDescriptor(Main.PLUGIN_ID)).getResource("wrench.png")));
        btn.addEventLink(CommandEvent.class, this, "editMap");
        rc.add(btn, li);
        btn = new BButton(new ImageIcon(manager.getPluginClassLoader(manager.getRegistry().getPluginDescriptor(Main.PLUGIN_ID)).getResource("minus.png")));
        btn.addEventLink(CommandEvent.class, this, "removeMap");
        rc.add(btn, li);
        boolean drawGrid = Boolean.parseBoolean(config.getProperty("flag.drawgrid", "true"));
        BCheckBox chbGrid = new BCheckBox(intn.getString("ME.DRAWGRID"), drawGrid);
        chbGrid.addEventLink(ValueChangedEvent.class, this, "changeGrid");
        rc.add(chbGrid, li);
        down.add(rc, 0, 2, new LayoutInfo(LayoutInfo.CENTER, LayoutInfo.HORIZONTAL));
        
        down.add(new BLabel(intn.getString("ME.LAYERS")), 1, 0, li);
        BScrollPane scrlL = new BScrollPane();
        lstLayers = new BList();
        lstLayers.addEventLink(SelectionChangedEvent.class, this, "layerChanged");
        scrlL.setContent(lstLayers);
        scrlL.setForceHeight(true);
        scrlL.setForceWidth(true);
        down.add(BOutline.createEtchedBorder(scrlL, false), 1, 1, new LayoutInfo(LayoutInfo.CENTER, LayoutInfo.BOTH, new Insets(5, 5, 5, 5), null));
        
        rc = new RowContainer();
        btn = new BButton(new ImageIcon(manager.getPluginClassLoader(manager.getRegistry().getPluginDescriptor(Main.PLUGIN_ID)).getResource("plus.png")));
        btn.addEventLink(CommandEvent.class, this, "addLayer");
        rc.add(btn, li);
        btn = new BButton(new ImageIcon(manager.getPluginClassLoader(manager.getRegistry().getPluginDescriptor(Main.PLUGIN_ID)).getResource("wrench.png")));
        btn.addEventLink(CommandEvent.class, this, "editLayer");
        rc.add(btn, li);
        btn = new BButton(new ImageIcon(manager.getPluginClassLoader(manager.getRegistry().getPluginDescriptor(Main.PLUGIN_ID)).getResource("arrow-090.png")));
        btn.addEventLink(CommandEvent.class, this, "layerUp");
        rc.add(btn, li);
        btn = new BButton(new ImageIcon(manager.getPluginClassLoader(manager.getRegistry().getPluginDescriptor(Main.PLUGIN_ID)).getResource("arrow-270.png")));
        btn.addEventLink(CommandEvent.class, this, "layerDown");
        rc.add(btn, li);
        btn = new BButton(new ImageIcon(manager.getPluginClassLoader(manager.getRegistry().getPluginDescriptor(Main.PLUGIN_ID)).getResource("minus.png")));
        btn.addEventLink(CommandEvent.class, this, "removeLayer");
        rc.add(btn, li);
        Boolean shadeLayers = Boolean.parseBoolean(config.getProperty("flag.shadelayers", "true"));
        BCheckBox chbShading = new BCheckBox(intn.getString("ME.SHADELAYERS"), shadeLayers);
        chbShading.addEventLink(ValueChangedEvent.class, this, "changeShading");
        rc.add(chbShading, li);
        down.add(rc, 1, 2, new LayoutInfo(LayoutInfo.CENTER, LayoutInfo.HORIZONTAL));
        
        BorderContainer bc = new BorderContainer();
        wTileSet = new TileSetWidget();
        wTileSet.setShowNumbers(true);
        bc.add(new BLabel(intn.getString("ME.TILESET")), BorderContainer.NORTH, li);
        bc.add(wTileSet, BorderContainer.CENTER, new LayoutInfo(LayoutInfo.CENTER, LayoutInfo.BOTH));
        Boolean showNumbers = Boolean.parseBoolean(config.getProperty("flag.shownumbers", "true"));
        BCheckBox chbNumbers = new BCheckBox(intn.getString("ME.SHOWNUMBERS"), showNumbers);
        wTileSet.setShowNumbers(showNumbers);
        chbNumbers.addEventLink(ValueChangedEvent.class, this, "changeNumbers");
        bc.add(chbNumbers, BorderContainer.SOUTH, li);
        
        FormContainer fc = new FormContainer(new double[]{3, 1}, new double[]{3, 1});
        
        fc.add(BOutline.createEtchedBorder(down, true), 0, 1, new LayoutInfo(LayoutInfo.SOUTH, LayoutInfo.BOTH));
        fc.add(BOutline.createEtchedBorder(bc, true), 1, 0, new LayoutInfo(LayoutInfo.EAST, LayoutInfo.BOTH));
        
        tabbed = new BTabbedPane();
        tabbed.addEventLink(SelectionChangedEvent.class, this, "tabChanged");
        
        editor = new MapEditorWidget();
        editor.setShadeLayers(shadeLayers);
        editor.setDrawGrid(drawGrid);
        tabbed.add(editor, intn.getString("GUI"));
        script = new ScriptArea();
        tabbed.add(script, intn.getString("SCRIPT"));
        
        fc.add(tabbed, 0, 0, new LayoutInfo(LayoutInfo.CENTER, LayoutInfo.BOTH));

        //init Tool Adapter
        Tools tooladapter = new Tools(wTileSet, editor);
        tooladapter.registerTool("Brush", new BrushTool(wTileSet, editor));
        tooladapter.registerTool("Rubber", new RubberTool(wTileSet, editor));
        wTileSet.addEventLink(WidgetMouseEvent.class, tooladapter, "changeTSSelection");
        editor.addEventLink(WidgetMouseEvent.class, tooladapter, "clickedOnMap");
        
        GridContainer toolsC = new GridContainer(3, 3);
        BButton button = new BButton(new ImageIcon(manager.getPluginClassLoader(descriptor).getResource("pencil.png")));
        button.setActionCommand("Brush");
        button.addEventLink(CommandEvent.class, tooladapter, "activatePlugin");
        toolsC.add(button, 0, 0);
        button = new BButton(new ImageIcon(manager.getPluginClassLoader(descriptor).getResource("eraser.png")));
        button.setActionCommand("Rubber");
        button.addEventLink(CommandEvent.class, tooladapter, "activatePlugin");
        toolsC.add(button, 1, 0);
        button = new BButton("SaveImage");
        //button.setActionCommand("Rubber");
        button.addEventLink(CommandEvent.class, this, "saveMapAsImage");
        toolsC.add(button, 0, 1);
        
        fc.add(toolsC, 1, 1, new LayoutInfo(LayoutInfo.CENTER, LayoutInfo.BOTH));
        
        container.add(fc, BorderContainer.CENTER, new LayoutInfo(LayoutInfo.CENTER, LayoutInfo.BOTH));
        
        enableAll(false);
    }
    
    private void enableAll(boolean en) {
        script.setEnabled(en);
        wTileSet.setEnabled(en);
        lstLayers.setEnabled(en);
        editor.setEnabled(en);
        script.setEnabled(en);
    }
    
    @Override
    public void save() {
        if (lstMaps.getSelectedIndex() > -1) {
            TileMap tilemap = (TileMap) lstMaps.getSelectedValue();
            generateScriptFile(tilemap);
        }
        
        log.info("Saving Configuration files...");
        
        config.setProperty("flag.drawgrid", Boolean.toString(editor.isDrawGrid()));
        config.setProperty("flag.shadelayers", Boolean.toString(editor.isShadeLayers()));
        config.setProperty("flag.shownumbers", Boolean.toString(wTileSet.isShowNumbers()));
        
        config.storeConfig();
    }
    
    private void generateScriptFile(TileMap tilemap) {
        Project project = Project.getInstance();
        String scrpt = "-- OpenRPGStudio TileMap Script --\n"
                + "-- Script is generated by application! Change with care! --\n\n";
        
        scrpt += tilemap.toScript();
        
        scrpt += "\nTilemaps.newTilemap(tilemap) \n";
        
        ScriptTools.getInstance().saveScriptFile(project.getScriptRegister().getAbsolutePath("tilemap_" + tilemap.getID()), scrpt);
    }
    
    private void tabChanged() {
        if (lstMaps.getSelectedIndex() > -1) {
            TileMap tilemap = (TileMap) lstMaps.getSelectedValue();
            if (tabbed.getSelectedTab() == 0) {
                LuaTable env = JsePlatform.standardGlobals();
                LuaThread.setGlobals(env);
                HashMap<String, LibFunction> libraries = Project.getInstance().getScriptRegister().getLibraries();
                
                for (LibFunction lib : libraries.values()) {
                    env.load(lib);
                }
                
                ScriptTools.getInstance().runScriptString(script.getText(), env);
                
                if (!env.get("tilemap").isnil()) {
                    tilemap.create(env.get("tilemap"));
                    lstLayers.setContents(tilemap.getLayers());
                    editor.setMap(tilemap);
                    generateScriptFile(tilemap);
                }
            } else {
                generateScriptFile(tilemap);
                script.setText(tilemap.toScript());
            }
        }
    }
    
    private void mapChanged() {
        if (lstMaps.getSelectedIndex() > -1) {
            TileMap tilemap = (TileMap) lstMaps.getSelectedValue();
            editor.setMap(tilemap);
            lstLayers.setContents(tilemap.getLayers());
            enableAll(true);
        } else {
            enableAll(false);
        }
    }
    
    private void addMap() {
        Project project = Project.getInstance();
        TileMap tilemap = new TileMap();
        EditMapDialog emd = new EditMapDialog(parent);
        if (emd.showDialog(tilemap)) {
            Database database = project.getDatabase();
            database.putScriptObject(TileMap.TYPE, tilemap.getID(), tilemap);
            lstMaps.add(tilemap);
            
            if (!project.getScriptRegister().isScriptRegistered("tilemap_" + tilemap.getID())) {
                project.getScriptRegister().registerScript("tilemap_" + tilemap.getID(), Paths.get("map_" + tilemap.getID() + ".lua"), -9);
                generateScriptFile(tilemap);
            }
        }
    }
    
    private void editMap() {
        Project project = Project.getInstance();
        if (lstMaps.getSelectedIndex() > -1) {
            TileMap tilemap = (TileMap) lstMaps.getSelectedValue();
            String oldID = tilemap.getID();
            EditMapDialog emd = new EditMapDialog(parent);
            if (emd.showDialog(tilemap)) {
                if (!oldID.equals(tilemap.getID())) {
                    Database database = Project.getInstance().getDatabase();
                    database.removeScriptObject(TileMap.TYPE, oldID);
                    database.putScriptObject(TileMap.TYPE, tilemap.getID(), tilemap);
                    
                    Path oldScript = project.getScriptRegister().getAbsolutePath("tilemap_" + oldID);
                    project.getScriptRegister().unregisterScript("tilemap_" + oldID);
                    
                    try {
                        Files.move(oldScript, project.getFolder("scripts").resolve("map_" + tilemap.getID() + ".lua"));
                    } catch (IOException ex) {
                        log.debug(ex);
                    }
                    
                    project.getScriptRegister().registerScript("tilemap_" + tilemap.getID(), Paths.get("map_" + tilemap.getID() + ".lua"), -9);
                }
                generateScriptFile(tilemap);
                editor.setMap(tilemap);
                lstMaps.repaint();
            }
        }
    }
    
    private void removeMap() {
        Internationalization intn = Internationalization.getInstance();
        if (lstMaps.getSelectedIndex() > -1) {
            TileMap tilemap = (TileMap) lstMaps.getSelectedValue();
            int r = JOptionPane.showConfirmDialog(parent.getComponent(), intn.getString("ME.REMOVEMAP"), null, JOptionPane.YES_NO_OPTION);
            if (r == JOptionPane.YES_OPTION) {
                Project.getInstance().getDatabase().removeScriptObject(TileMap.TYPE, tilemap.getID());
                try {
                    Files.deleteIfExists(Project.getInstance().getScriptRegister().getAbsolutePath("tilemap_" + tilemap.getID()));
                } catch (IOException ex) {
                    log.debug(ex);
                }
                Project.getInstance().getScriptRegister().unregisterScript("tilemap_" + tilemap.getID());
                lstMaps.remove(lstMaps.getSelectedIndex());
                lstMaps.clearSelection();
            }
        }
    }
    
    private void layerChanged() {
        if (lstMaps.getSelectedIndex() > -1) {
            if (lstLayers.getSelectedIndex() > -1) {
                Layer layer = (Layer) lstLayers.getSelectedValue();
                if (layer.getType().equals(Layer.TYPE.TILE)) {
                    wTileSet.setTileSet(layer.getTileset());
                } else {
                    TileSet ts = new TileSet();
                    ts.setID("###EVENTSET###");
                    ts.setTileHeight(64);
                    ts.setTileWidth(64);
                    try {
                        ts.setImage(new Media(Media.IMAGE, "EVENTSET", Paths.get(manager.getPluginClassLoader(descriptor).getResource("eventset.png").toURI())));
                    } catch (URISyntaxException ex) {
                        log.error(ex);
                    }
                    layer.setTileset(ts);
                    wTileSet.setTileSet(ts);
                }
                
                editor.setSelectedLayer(layer);
            }
        }
    }
    
    private void addLayer() {
        if (lstMaps.getSelectedIndex() > -1) {
            TileMap tilemap = (TileMap) lstMaps.getSelectedValue();
            Layer layer = new Layer();
            EditLayerDialog eld = new EditLayerDialog(parent);
            if (eld.showDialog(layer)) {
                tilemap.addLayer(layer);
                lstLayers.add(layer);
                editor.setMap(tilemap);
                generateScriptFile(tilemap);
            }
        }
    }
    
    private void editLayer() {
        if (lstMaps.getSelectedIndex() > -1) {
            TileMap tilemap = (TileMap) lstMaps.getSelectedValue();
            if (lstLayers.getSelectedIndex() > -1) {
                Layer layer = (Layer) lstLayers.getSelectedValue();
                EditLayerDialog eld = new EditLayerDialog(parent);
                if (eld.showDialog(layer)) {
                    lstLayers.repaint();
                    editor.setMap(tilemap);
                    generateScriptFile(tilemap);
                }
            }
        }
    }
    
    private void removeLayer() {
        Internationalization intn = Internationalization.getInstance();
        if (lstMaps.getSelectedIndex() > -1) {
            TileMap tilemap = (TileMap) lstMaps.getSelectedValue();
            if (lstLayers.getSelectedIndex() > -1) {
                Layer layer = (Layer) lstLayers.getSelectedValue();
                int r = JOptionPane.showConfirmDialog(parent.getComponent(), intn.getString("ME.REMOVELAYER"), null, JOptionPane.YES_NO_OPTION);
                if (r == JOptionPane.YES_OPTION) {
                    tilemap.getLayers().remove(layer);
                    lstLayers.remove(lstLayers.getSelectedIndex());
                    lstLayers.clearSelection();
                    editor.setMap(tilemap);
                    generateScriptFile(tilemap);
                }
            }
        }
    }
    
    private void layerUp() {
        if (lstMaps.getSelectedIndex() > -1) {
            TileMap tilemap = (TileMap) lstMaps.getSelectedValue();
            if (lstLayers.getSelectedIndex() > -1) {
                Layer layer = (Layer) lstLayers.getSelectedValue();
                tilemap.layerUp(layer);
                lstLayers.setContents(tilemap.getLayers());
                editor.repaint();
                generateScriptFile(tilemap);
            }
        }
    }
    
    private void layerDown() {
        if (lstMaps.getSelectedIndex() > -1) {
            TileMap tilemap = (TileMap) lstMaps.getSelectedValue();
            if (lstLayers.getSelectedIndex() > -1) {
                Layer layer = (Layer) lstLayers.getSelectedValue();
                tilemap.layerDown(layer);
                lstLayers.setContents(tilemap.getLayers());
                editor.repaint();
                generateScriptFile(tilemap);
            }
        }
    }
    
    private void changeNumbers(ValueChangedEvent ev) {
        wTileSet.setShowNumbers(((BCheckBox) ev.getWidget()).getState());
    }
    
    private void changeShading(ValueChangedEvent ev) {
        editor.setShadeLayers(((BCheckBox) ev.getWidget()).getState());
    }
    
    private void changeGrid(ValueChangedEvent ev) {
        editor.setDrawGrid(((BCheckBox) ev.getWidget()).getState());
    }
    
    private void saveMapAsImage() {
        BFileChooser bfc = new BFileChooser(BFileChooser.SAVE_FILE, "Save image to...");
        if (bfc.showDialog(parent)) {
            try {
                editor.saveMapAsImage(bfc.getSelectedFile().toPath());
            } catch (IOException ex) {
                log.error(ex);
            }
        }
    }
}
